/**
 * 
 */
package com.vmware.array2d.util;

import java.util.Iterator;

/**
 * Spriral iterator for a 2DArray
 * 
 * @author anshuman
 * 
 */
public class SpriralIterator<T> implements Iterator<T> {
    // Spiral iteration required traversing right, down, left and up direction
    enum directions {
        right, down, left, up
    };
    // 2DArray
    private T[][] array2d;
    // current row index
    private int currentRowIndx = 0;
    // current column index
    private int currentColIndx = -1;
    private int rows; // rows#
    private int cols; // cols#
    // Spiral layer number. Its defaulted to 0.
    private int spiralLayerNo = 0;

    // Current direction of the iteration. It starts with right direction, followed by down, left and up direction
    private directions currentDirection = directions.right;
    // total number of elements seen so far
    private int noOfElementsTraverssed = 0;

    /**
     * @param array2d
     * @param rows
     * @param cols
     */
    public SpriralIterator(T[][] array2d) {
        this.array2d = array2d;
        this.rows = array2d.length;
        this.cols = this.rows > 0 ? array2d[0].length : 0;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean hasNext() {
        return (noOfElementsTraverssed < (rows * cols));
    }

    @Override
    public T next() {
        if (!hasNext())
            throw new RuntimeException(
                    "First use the hasNext() method to check the next element exist then call this method.");

        switch (currentDirection) {
            case right :
                updateCurrentCursorForRightDirection();

                break;
            case down :
                updateCurrentCursorForDownDirection();
                break;
            case left :
                updateCurrentCursorForLeftDirection();
                break;
            case up :
                updateCurrentCursorForUpDirection();
                break;
            default :
                throw new RuntimeException(); // this will never happen
        }
        noOfElementsTraverssed++;
        return array2d[currentRowIndx][currentColIndx];
    }

    /**
     * Updates the current row index or current col index to fetch next element
     */
    private void updateCurrentCursorForRightDirection() {
        if (isEndOfRight()) {
            currentDirection = directions.down;
            currentRowIndx++;
        } else {
            currentColIndx++;
        }
    }

    /**
     * Updates the current row index or current col index to fetch next element
     */
    private void updateCurrentCursorForDownDirection() {
        if (isEndOfDown()) {
            currentDirection = directions.left;
            currentColIndx--;
        } else {
            currentRowIndx++;
        }
    }

    /**
     * Updates the current row index or current col index to fetch next element
     */
    private void updateCurrentCursorForLeftDirection() {
        if (isEndOfLeft()) {
            currentDirection = directions.up;
            currentRowIndx--;
        } else {
            currentColIndx--;
        }
    }

    /**
     * Updates the current row index or current col index to fetch next element
     */
    private void updateCurrentCursorForUpDirection() {
        if (isEndOfUp()) {
            currentDirection = directions.right;
            currentColIndx++;
            spiralLayerNo++; // increment the layer number
        } else {
            currentRowIndx--;
        }
    }

    /**
     * @return false if the next cursor position crosses the 2DArray boundary
     */
    private boolean isEndOfRight() {
        return ((currentColIndx + 1) > (cols - 1 - spiralLayerNo));
    }

    /**
     * @return false if the next cursor position crosses the 2DArray boundary
     */
    private boolean isEndOfDown() {
        return ((currentRowIndx + 1) > (rows - 1 - spiralLayerNo));
    }

    /**
     * @return false if the next cursor position crosses the 2DArray boundary
     */
    private boolean isEndOfLeft() {
        return ((currentColIndx - 1) < spiralLayerNo);
    }

    /**
     * @return false if the next cursor position crosses the 2DArray boundary
     */
    private boolean isEndOfUp() {
        return ((currentRowIndx - 1) < (spiralLayerNo + 1));
    }
}
